﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;


namespace DDD {

    /// <summary>
    /// Uniform変数を保存するコンテナクラス
    /// </summary>
    /// <remarks>
    /// <see cref="UniformArray"/>はUniform変数を保存するコンテナクラスです。
    /// 複数のUniform変数を一括して管理することが可能です。
    /// Uniform変数は必ずこの<see cref=" UniformArray"/>オブジェクト単位で追加します。
    /// また<see cref="UniformArray"/>オブジェクトはいくつでも追加できるので、
    /// 例えばUniform変数の種類毎に用意する事が可能です。
    /// </remarks>
    public class UniformArray : Object, IEnumerable<UniformVariable> {

        #region Field
        List<UniformVariable> uniforms;

        /// <summary>
        /// アクティブカメラを指す特殊ノード
        /// </summary>
        /// <remarks>
        /// このノードが指定された場合プロパティの参照元として常に最新の<see cref="World.ActiveCamera"/>を参照します。
        /// この特殊ノードではなく直接<see cref="World.ActiveCamera"/>を指定した場合はアクティブカメラを切り替えた時に
        /// 古い方のカメラを参照し続けます。
        /// </remarks>
        public static Camera CurrentActiveCamera = new Camera ();

        /// <summary>
        /// この<see cref="UniformArray"/>オブジェクトがアタッチされたノードを示す特殊ノード
        /// </summary>
        /// <remarks>
        /// Uniform変数の参照元オブジェクトとしてこの特殊ノードを指定すると､
        /// 特定のオブジェクトにではなくこの<see cref="UniformArray"/>がアタッチされたノードを参照します｡
        /// </remarks>
        public static Node CurrentNode = new Node();

        /// <summary>
        /// この<see cref="UniformArray"/>オブジェクトがアタッチされたマテリアルを示す特殊ノード
        /// </summary>
        /// <remarks>
        /// Uniform変数の参照元オブジェクトとしてこの特殊マテリアルを指定すると､
        /// 特定のオブジェクトではなくこの<see cref="UniformArray"/>オブジェクトがアタッチされたアピアランスの
        /// マテリアルを参照します｡
        /// </remarks>
        public static Material CurrentMaterial = new Material ();
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        public UniformArray () {
            this.uniforms = new List<UniformVariable> ();
        }
        #endregion

        #region Property
        /// <summary>
        /// 現在このアレイにセットされているUnfiorm変数の個数
        /// </summary>
        public int Count {
            get { return uniforms.Count; }
        }

        /// <summary>
        /// 指定の名前のUniform変数の取得するインデクサー
        /// </summary>
        /// <remarks>
        /// その名前のUniform変数が登録されていない場合、
        /// デフォルトの<see cref="UniformVariable"/>構造体の規定値が返ります。
        /// </remarks>
        /// <param name="name">Uniform変数名</param>
        /// <returns>Uniform変数を表す<see cref="UniformVariable"/>構造体</returns>
        public UniformVariable this[string name] {
            get {
                return Get (name);
            }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                return base.References;
            }
        }
        #endregion

        #region Method
        /// <summary>
        /// <dic>IEnumerable</dic>インターフェースの実装
        /// </summary>
        /// <returns></returns>
        IEnumerator<UniformVariable> IEnumerable<UniformVariable>.GetEnumerator () {
            return ((IEnumerable<UniformVariable>)uniforms).GetEnumerator ();
        }

        /// <summary>
        /// <dic>IEnumerable</dic>インターフェースの実装
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
            return uniforms.GetEnumerator ();
        }

        /// <summary>
        /// 指定のオブジェクトの指定のプロパティを指定の名前のUniform変数として登録します
        /// </summary>
        /// <remarks>
        /// このアレイにUniform変数<paramref name="name"/>を追加し、オブジェクトのプロパティ<paramref name="e"/>をバインドします。
        /// 初期値は0です。
        /// <see cref="World.Reflesh"/>が呼ばれるとプロパティから値が読み取られ指定のUniform変数にセットされます。
        /// 配列型のUniform変数/プロパティもこのメソッドを使用します。
        /// 配列の長さはシェーダーで定義された値を使用します。プロパティが配列型でないまたは長さがそれより短い場合エラーになります。
        /// 既に登録されている名前と同名のUniform変数を登録する場合は古い方は消されて新しいものに置き換わります。
        /// </remarks>
        /// <typeparam name="TObj">ソースオブジェクトの型</typeparam>
        /// <typeparam name="T">プロパティの型</typeparam>
        /// <param name="name">Uniform変数名</param>
        /// <param name="src">オブジェクトソース</param>
        /// <param name="e">プロパティを表す式木</param>
        public void Add<TObj, T> (string name, TObj src, Expression<Func<T>> e) where TObj : Object {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            if (src == null) {
                throw new ArgumentNullException ("Source is null");
            }
            if (e == null) {
                throw new ArgumentNullException ("Expression is null");
            }
            var propName = Object.GetName (e);
            //var propInfo = src.GetType().GetProperty (propName);
            //if (propInfo == null) {
            //    throw new ArgumentException ("Can't find Property(\"" + propName + "\") at " + src);
            //}

            this.uniforms.RemoveAll (x => x.Name == name);
            this.uniforms.Add (new UniformVariable (name, src, propName));
        }

        /// <summary>
        /// Uniform変数に即値を指定します
        /// </summary>
        /// <remarks>
        /// 即値には標準の組み込み型と<see cref="IExportable{T}"/>型を継承した構造体を指定可能です｡
        /// (メモ)これのチェックを省略している｡実装はよ
        /// (メモ) 2012/11/16 where T: structを削除した。サンプラーを即値指定するための修正
        /// 何でも指定できるのは微妙・・・
        /// </remarks>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">Uniform変数名</param>
        /// <param name="src">即値</param>
        public void Add<T> (string name, T src) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }

            this.uniforms.RemoveAll (x => x.Name == name);
            this.uniforms.Add (new UniformVariable (name, src, null));
        }


        /// <summary>
        /// Uniform変数を削除します
        /// </summary>
        /// <remarks>
        /// 指定の名前のUniform変数が登録されていない場合これを無視します。
        /// </remarks>
        /// <param name="name">削除したいUniform変数の名前</param>
        /// <returns>削除した要素の数</returns>
        public int Remove (string name) {
            return uniforms.RemoveAll (x => x.Name == name);
        }

        /// <summary>
        /// 指定の名前のUniform変数を取得します
        /// </summary>
        /// <remarks>
        /// 指定の名前のUniform変数が存在しない場合は例外が発生します。
        /// </remarks>
        /// <param name="name">Uniform変数名</param>
        /// <returns>Uniform変数</returns>
        public UniformVariable Get (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null");
            }
            if (!(uniforms.Exists (x => x.Name == name))) {
                throw new ArgumentException("Name was not found");
            }

            return uniforms.Find (x => x.Name == name);
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("UniformArray: Count={0}", Count);
        }

        #endregion


    }
}
